package org.carpediem.conman.dao;

import org.apache.commons.io.IOUtils;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocCollector;
import org.apache.pdfbox.searchengine.lucene.LucenePDFDocument;
import org.carpediem.conman.Util;
import org.carpediem.conman.entity.Contact;
import org.carpediem.conman.entity.Document;
import org.carpediem.conman.entity.Note;
import org.carpediem.conman.server.Lucene;
import org.carpediem.conman.server.S3Persister;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.HibernateTemplate;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author Mark Petrovic
 */
abstract public class AbstractDao {
    private static final Logger logger = Logger.getLogger(AbstractDao.class.getName());

    protected HibernateTemplate hibernateTemplate;

    private S3Persister s3Persister;

    public void init() {

    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        hibernateTemplate = new HibernateTemplate(sessionFactory);
    }

    protected void index(Contact contact) {
        try {
            IndexWriter writer = new IndexWriter(new File(Lucene.INDEX_DIR), new StandardAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);

            // Because we cycle thru all documents bound to a contact, we need to protect against those that are not actually being
            // indexed this time around.  Do not index if buffer == null.
            for (Document doc : contact.getDocument()) {
                if (!indexed(Lucene.URI, doc.getUri().toString()) && doc.getBuffer() != null) {
                    org.apache.lucene.document.Document luceneDoc = LucenePDFDocument.getDocument(new ByteArrayInputStream(doc.getBuffer()));
                    luceneDoc.add(new Field(Contact.CONTACT, contact.getEmailAddress(), Field.Store.YES, Field.Index.NO));
                    luceneDoc.add(new Field(Document.ORIGINAL_NAME, doc.getOriginalName(), Field.Store.YES, Field.Index.NO));
                    luceneDoc.add(new Field(Lucene.URI, doc.getUri().toString(), Field.Store.YES, Field.Index.NO));
                    luceneDoc.add(new Field(Lucene.TYPE, Lucene.DOCUMENT_TYPES.DOCUMENT.toString(), Field.Store.YES, Field.Index.NO));
                    writer.addDocument(luceneDoc);
                    logger.info("Document " + contact.getEmailAddress() + ":" + doc.getOriginalName() + " indexed");
                }
            }
            for (Note note : contact.getNote()) {
                if (!indexed(Lucene.URI, note.getUri().toString())) {
                    org.apache.lucene.document.Document luceneDoc = new org.apache.lucene.document.Document();
                    luceneDoc.add(new Field(Contact.CONTACT, contact.getEmailAddress(), Field.Store.YES, Field.Index.NO));
                    luceneDoc.add(new Field(Lucene.URI, note.getUri().toString(), Field.Store.YES, Field.Index.NO));
                    luceneDoc.add(new Field(Lucene.TYPE, Lucene.DOCUMENT_TYPES.NOTE.toString(), Field.Store.YES, Field.Index.NO));
                    luceneDoc.add(new Field(Note.TEXT, note.getNote(), Field.Store.YES, Field.Index.ANALYZED));
                    writer.addDocument(luceneDoc);
                    logger.info("Note-" + note.getId() + " of " + contact.getEmailAddress() + " indexed");
                }
            }
            writer.close();
        }
        catch (IOException e) {
            logger.log(Level.WARNING, "Cannot create Lucene indexer.  Returning.", e);
            return;
        }
    }

    private boolean indexed(String key, String value) {
        try {
            QueryParser parser = new QueryParser(key, new StandardAnalyzer());
            org.apache.lucene.search.Query query = parser.parse(value);
            IndexSearcher is = new IndexSearcher(Lucene.INDEX_DIR);
            TopDocCollector tDocCollector = new TopDocCollector(1);
            is.search(query, tDocCollector);
            ScoreDoc[] hits = tDocCollector.topDocs().scoreDocs;
            return hits.length > 0;
        } catch (IOException e) {
            logger.log(Level.WARNING, "IO Exception during index check", e);
        } catch (ParseException e) {
            logger.log(Level.WARNING, "Parse Exception during index check", e);
        }

        return false;
    }

    protected void cacheLocally(Contact contact) {
        for (Document doc : contact.getDocument()) {
            if (doc.getBuffer() != null) {
                File fout = localFile(contact, doc);
                try {
                    FileOutputStream fos = new FileOutputStream(fout);
                    ByteArrayInputStream bis = new ByteArrayInputStream(doc.getBuffer());
                    IOUtils.copy(bis, fos);
                    bis.close();
                    fos.close();
                    logger.info("Document cached locally: " + fout.getAbsolutePath());
                } catch (IOException e) {
                    logger.log(Level.WARNING, "error caching locally", e);
                }
            }
        }
    }

    private File localFile(Contact contact, Document doc) {
        File dir = dirName(contact);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        String pf = Util.hexDigest(doc.getBuffer());
        File fout = new File(dir, pf + "-" + doc.getOriginalName());
        return fout;
    }

    private File dirName(Contact contact) {
        String prefix = "_";
        int binLen = 4;
        String bin = prefix + contact.getEmailAddress().substring(0, binLen);
        File dirName = new File(new File(System.getProperties().getProperty("user.home"), "conman"), bin);
        return dirName;
    }

    public S3Persister getS3Persister() {
        return s3Persister;
    }

    public void setS3Persister(S3Persister s3Persister) {
        this.s3Persister = s3Persister;
    }
}
